
package com.ctrip.corp.obt.shard.sql.mybatis.interceptor.inner;

import com.ctrip.corp.obt.generic.utils.CollectionUtils;
import com.ctrip.corp.obt.generic.utils.EnvironmentHolder;
import com.ctrip.corp.obt.generic.utils.StringPool;
import com.ctrip.corp.obt.shard.configuration.DataShardProperties;
import com.ctrip.corp.obt.shard.context.PermissionContext;
import com.ctrip.corp.obt.shard.holder.InnerPluginIgnoreHolder;
import com.ctrip.corp.obt.shard.holder.PermissionHolder;
import com.ctrip.corp.obt.shard.sql.parser.AbstractSqlParserSupport;
import com.ctrip.corp.obt.shard.sql.parser.permission.PermissionHandler;
import com.ctrip.corp.obt.shard.utils.ClassUtils;
import com.ctrip.corp.obt.shard.utils.PluginUtils;
import com.ctrip.corp.obt.shard.utils.PropertiesWrapper;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.ToString;
import net.sf.jsqlparser.expression.BinaryExpression;
import net.sf.jsqlparser.expression.Expression;
import net.sf.jsqlparser.expression.Function;
import net.sf.jsqlparser.expression.NotExpression;
import net.sf.jsqlparser.expression.Parenthesis;
import net.sf.jsqlparser.expression.operators.conditional.AndExpression;
import net.sf.jsqlparser.expression.operators.conditional.OrExpression;
import net.sf.jsqlparser.expression.operators.relational.ExistsExpression;
import net.sf.jsqlparser.expression.operators.relational.ExpressionList;
import net.sf.jsqlparser.expression.operators.relational.InExpression;
import net.sf.jsqlparser.expression.operators.relational.ItemsList;
import net.sf.jsqlparser.schema.Column;
import net.sf.jsqlparser.schema.Table;
import net.sf.jsqlparser.statement.delete.Delete;
import net.sf.jsqlparser.statement.insert.Insert;
import net.sf.jsqlparser.statement.select.FromItem;
import net.sf.jsqlparser.statement.select.Join;
import net.sf.jsqlparser.statement.select.LateralSubSelect;
import net.sf.jsqlparser.statement.select.PlainSelect;
import net.sf.jsqlparser.statement.select.Select;
import net.sf.jsqlparser.statement.select.SelectExpressionItem;
import net.sf.jsqlparser.statement.select.SelectItem;
import net.sf.jsqlparser.statement.select.SubJoin;
import net.sf.jsqlparser.statement.select.SubSelect;
import net.sf.jsqlparser.statement.select.ValuesList;
import net.sf.jsqlparser.statement.select.WithItem;
import net.sf.jsqlparser.statement.update.Update;
import org.apache.ibatis.executor.Executor;
import org.apache.ibatis.executor.statement.StatementHandler;
import org.apache.ibatis.mapping.BoundSql;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.mapping.SqlCommandType;
import org.apache.ibatis.session.ResultHandler;
import org.apache.ibatis.session.RowBounds;

import java.sql.Connection;
import java.sql.SQLException;
import java.util.Collection;
import java.util.Deque;
import java.util.LinkedList;
import java.util.List;
import java.util.Properties;

import static com.ctrip.corp.obt.shard.holder.PermissionHolder.preferOrgPermission;

/**
 * 数据权限拦截器
 *
 * @author marco.zheng
 * @since 2022-02-10
 */
@Data
@ToString(callSuper = true)
@EqualsAndHashCode(callSuper = true)
@SuppressWarnings({"rawtypes"})
public class PermissionInnerPlugin extends AbstractSqlParserSupport implements InnerPlugin {

    private PermissionHandler permissionHandler;

    @Override
    public boolean skipPluginQuery(Executor executor, MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException {
        return !PermissionHolder.needPermission(ms.getId());
    }

    @Override
    public void beforeQuery(Executor executor, MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException {
        if (PermissionContext.getOrgIds() == null && PermissionContext.getCorpIds() == null) {
            if (log.isDebugEnabled()) {
                log.debug("No org ids and no corp ids was found, will skip beforeQuery");
            }
            return;
        }
        if (InnerPluginIgnoreHolder.willIgnoreTenant(ms.getId())) {
            return;
        }
        PluginUtils.DSBoundSql mpBs = PluginUtils.dsBoundSql(boundSql);
        mpBs.sql(parserSingle(mpBs.sql(), null));
    }

    @Override
    public void beforePrepare(StatementHandler sh, Connection connection, Integer transactionTimeout) {
        if (PermissionContext.getOrgIds() == null && PermissionContext.getCorpIds() == null) {
            if (log.isDebugEnabled()) {
                log.debug("No org ids and no corp ids was found, will skip beforePrepare");
            }
            return;
        }
        PluginUtils.DSStatementHandler dsSh = PluginUtils.dsStatementHandler(sh);
        MappedStatement ms = dsSh.mappedStatement();
        SqlCommandType sct = ms.getSqlCommandType();
        if (sct == SqlCommandType.INSERT || sct == SqlCommandType.UPDATE || sct == SqlCommandType.DELETE) {
            if (InnerPluginIgnoreHolder.willIgnoreTenant(ms.getId())) {
                return;
            }
            PluginUtils.DSBoundSql mpBs = dsSh.dsBoundSql();
            mpBs.sql(parserMulti(mpBs.sql(), null));
        }
    }

    @Override
    protected void processDelete(Delete delete, int index, String sql, Object obj) {
        // Do nothing
    }

    @Override
    protected void processInsert(Insert insert, int index, String sql, Object obj) {
        // Do nothing
    }

    @Override
    protected void processUpdate(Update update, int index, String sql, Object obj) {
        // Do nothing
    }

    @Override
    protected void processSelect(Select select, int index, String sql, Object obj) {
        if (PermissionContext.getOrgIds() == null && PermissionContext.getCorpIds() == null) {
            if (log.isDebugEnabled()) {
                log.debug("No org ids and no corp ids was found, will skip processSelect");
            }
            return;
        }
        processSelectBody(select.getSelectBody());
        List<WithItem> withItemsList = select.getWithItemsList();
        if (!CollectionUtils.isEmpty(withItemsList)) {
            withItemsList.forEach(this::processSelectBody);
        }
    }


    /**
     * 处理 PlainSelect
     */
    protected void processPlainSelect(PlainSelect plainSelect) {
        FromItem fromItem = plainSelect.getFromItem();
        Expression where = plainSelect.getWhere();
        processWhereSubSelect(where);
        if (fromItem instanceof Table) {
            Table fromTable = (Table) fromItem;
            Expression expression = builderExpression(where, fromTable);
            if (!permissionHandler.ignoreTable(fromTable.getName()) && expression != null) {
                plainSelect.setWhere(expression);
            }
        } else {
            processFromItem(fromItem);
        }
        List<SelectItem> selectItems = plainSelect.getSelectItems();
        if (CollectionUtils.isNotEmpty(selectItems)) {
            selectItems.forEach(this::processSelectItem);
        }
        List<Join> joins = plainSelect.getJoins();
        if (CollectionUtils.isNotEmpty(joins)) {
            processJoins(joins);
        }
    }

    /**
     * 处理where条件内的子查询
     * <p>
     * 支持如下:
     * 1. in
     * 2. =
     * 3. >
     * 4. <
     * 5. >=
     * 6. <=
     * 7. <>
     * 8. EXISTS
     * 9. NOT EXISTS
     * <p>
     * 前提条件:
     * 1. 子查询必须放在小括号中
     * 2. 子查询一般放在比较操作符的右边
     *
     * @param where where 条件
     */
    protected void processWhereSubSelect(Expression where) {
        if (where == null) {
            return;
        }
        if (where instanceof FromItem) {
            processFromItem((FromItem) where);
            return;
        }
        if (where.toString().indexOf("SELECT", 1) > -1) {
            // 有子查询
            if (where instanceof BinaryExpression) {
                // 比较符号 , and , or , 等等
                BinaryExpression expression = (BinaryExpression) where;
                processWhereSubSelect(expression.getLeftExpression());
                processWhereSubSelect(expression.getRightExpression());
            } else if (where instanceof InExpression) {
                // in
                InExpression expression = (InExpression) where;
                ItemsList itemsList = expression.getRightItemsList();
                if (itemsList instanceof SubSelect) {
                    processSelectBody(((SubSelect) itemsList).getSelectBody());
                }
            } else if (where instanceof ExistsExpression) {
                // exists
                ExistsExpression expression = (ExistsExpression) where;
                processWhereSubSelect(expression.getRightExpression());
            } else if (where instanceof NotExpression) {
                // not exists
                NotExpression expression = (NotExpression) where;
                processWhereSubSelect(expression.getExpression());
            } else if (where instanceof Parenthesis) {
                Parenthesis expression = (Parenthesis) where;
                processWhereSubSelect(expression.getExpression());
            }
        }
    }

    private void processSelectItem(SelectItem selectItem) {
        if (selectItem instanceof SelectExpressionItem) {
            SelectExpressionItem selectExpressionItem = (SelectExpressionItem) selectItem;
            if (selectExpressionItem.getExpression() instanceof SubSelect) {
                processSelectBody(((SubSelect) selectExpressionItem.getExpression()).getSelectBody());
            } else if (selectExpressionItem.getExpression() instanceof Function) {
                processFunction((Function) selectExpressionItem.getExpression());
            }
        }
    }

    /**
     * 处理函数
     * <p>支持: 1. select fun(args..) 2. select fun1(fun2(args..),args..)<p>
     * <p> fixed gitee pulls/141</p>
     *
     * @param function
     */
    private void processFunction(Function function) {
        ExpressionList parameters = function.getParameters();
        if (parameters != null) {
            parameters.getExpressions().forEach(expression -> {
                if (expression instanceof SubSelect) {
                    processSelectBody(((SubSelect) expression).getSelectBody());
                } else if (expression instanceof Function) {
                    processFunction((Function) expression);
                }
            });
        }
    }

    /**
     * 处理子查询等
     */
    private void processFromItem(FromItem fromItem) {
        if (fromItem instanceof SubJoin) {
            SubJoin subJoin = (SubJoin) fromItem;
            if (subJoin.getJoinList() != null) {
                processJoins(subJoin.getJoinList());
            }
            if (subJoin.getLeft() != null) {
                processFromItem(subJoin.getLeft());
            }
        } else if (fromItem instanceof SubSelect) {
            SubSelect subSelect = (SubSelect) fromItem;
            if (subSelect.getSelectBody() != null) {
                processSelectBody(subSelect.getSelectBody());
            }
        } else if (fromItem instanceof ValuesList) {
            log.debug("Perform a subquery, if you do not give us feedback");
        } else if (fromItem instanceof LateralSubSelect) {
            LateralSubSelect lateralSubSelect = (LateralSubSelect) fromItem;
            if (lateralSubSelect.getSubSelect() != null) {
                SubSelect subSelect = lateralSubSelect.getSubSelect();
                if (subSelect.getSelectBody() != null) {
                    processSelectBody(subSelect.getSelectBody());
                }
            }
        }
    }

    /**
     * 处理 joins
     *
     * @param joins join 集合
     */
    private void processJoins(List<Join> joins) {
        //对于 on 表达式写在最后的 join，需要记录下前面多个 on 的表名
        Deque<Table> tables = new LinkedList<>();
        for (Join join : joins) {
            // 处理 on 表达式
            FromItem fromItem = join.getRightItem();
            if (fromItem instanceof Table) {
                Table fromTable = (Table) fromItem;
                // 获取 join 尾缀的 on 表达式列表
                Collection<Expression> originOnExpressions = join.getOnExpressions();
                // 正常 join on 表达式只有一个，立刻处理
                if (originOnExpressions.size() == 1) {
                    processJoin(join);
                    continue;
                }
                // 当前表是否忽略
                boolean needIgnore = permissionHandler.ignoreTable(fromTable.getName());
                // 表名压栈，忽略的表压入 null，以便后续不处理
                tables.push(needIgnore ? null : fromTable);
                // 尾缀多个 on 表达式的时候统一处理
                if (originOnExpressions.size() > 1) {
                    Collection<Expression> onExpressions = new LinkedList<>();
                    for (Expression originOnExpression : originOnExpressions) {
                        Table currentTable = tables.poll();
                        Expression expression = builderExpression(originOnExpression, currentTable);
                        if (currentTable == null || expression == null) {
                            onExpressions.add(originOnExpression);
                        } else {
                            onExpressions.add(expression);
                        }
                    }
                    join.setOnExpressions(onExpressions);
                }
            } else {
                // 处理右边连接的子表达式
                processFromItem(fromItem);
            }
        }
    }

    /**
     * 处理联接语句
     */
    protected void processJoin(Join join) {
        if (join.getRightItem() instanceof Table) {
            Table fromTable = (Table) join.getRightItem();
            if (permissionHandler.ignoreTable(fromTable.getName())) {
                // 过滤退出执行
                return;
            }
            // 走到这里说明 on 表达式肯定只有一个
            Collection<Expression> originOnExpressions = join.getOnExpressions();
            List<Expression> onExpressions = new LinkedList<>();
            Expression currentExpression = originOnExpressions.iterator().next();
            Expression expression = builderExpression(originOnExpressions.iterator().next(), fromTable);
            if (expression != null) {
                onExpressions.add(expression);
            } else {
                onExpressions.add(currentExpression);
            }
            join.setOnExpressions(onExpressions);
        }
    }

    /**
     * 处理条件
     */
    protected Expression builderExpression(Expression currentExpression, Table table) {
        DataShardProperties properties = EnvironmentHolder.getBean(DataShardProperties.class);
        // 只有当黑名单为空，并且白名单包含该table时，才会添加权限处理
        if (CollectionUtils.isEmpty(properties.getPermissionTableBlackList())
                && !properties.getPermissionTableWhiteList().contains(table.getName())) {
            return null;
        }
        // 如果黑名单不为空，且包含该table时，跳过该表权限处理
        if (properties.getPermissionTableBlackList().contains(table.getName())) {
            return null;
        }
        InExpression in = new InExpression();
        in.setLeftExpression(this.getAliasColumn(table));
        if (preferOrgPermission()) {
            in.setRightItemsList(permissionHandler.getOrgIds());
        } else {
            in.setRightItemsList(permissionHandler.getCorpIds());
        }
        if (currentExpression == null) {
            return in;
        }
        if (currentExpression instanceof OrExpression) {
            return new AndExpression(new Parenthesis(currentExpression), in);
        } else {
            return new AndExpression(currentExpression, in);
        }
    }


    /**
     * 租户字段别名设置
     * <p>orgId 或 tableAlias.orgId</p>
     *
     * @param table 表对象
     * @return 字段
     */
    protected Column getAliasColumn(Table table) {
        StringBuilder column = new StringBuilder();
        if (table.getAlias() != null) {
            column.append(table.getAlias().getName()).append(StringPool.DOT);
        }
        if (preferOrgPermission()) {
            column.append(permissionHandler.getOrgIdColumn());
        } else {
            column.append(permissionHandler.getCorpIdColumn());
        }

        return new Column(column.toString());
    }

    @Override
    public void setProperties(Properties properties) {
        PropertiesWrapper.newInstance(properties).whenNotBlank("permissionHandler",
                ClassUtils::newInstance, this::setPermissionHandler);
    }

    @Override
    public int order() {
        return -101;
    }
}


